package com.jakewharton.telecine; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; import android.content.Context; import android.support.annotation.NonNull; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.TextView; import butterknife.BindDimen; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import java.util.Locale; import static android.graphics.PixelFormat.TRANSLUCENT; import static android.text.TextUtils.getLayoutDirectionFromLocale; import static android.view.ViewAnimationUtils.createCircularReveal; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; @SuppressLint("ViewConstructor") // Lint, in this case, I am smarter than you. final class OverlayView extends FrameLayout { private static final int COUNTDOWN_DELAY = 1000; private static final int NON_COUNTDOWN_DELAY = 500; private static final int DURATION_ENTER_EXIT = 300; static OverlayView create(Context context, Listener listener, boolean showCountDown) { return new OverlayView(context, listener, showCountDown); } static WindowManager.LayoutParams createLayoutParams(Context context) { int width = context.getResources().getDimensionPixelSize(R.dimen.overlay_width); final WindowManager.LayoutParams params = new WindowManager.LayoutParams(width, WRAP_CONTENT, TYPE_SYSTEM_ERROR, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_NO_LIMITS | FLAG_LAYOUT_INSET_DECOR | FLAG_LAYOUT_IN_SCREEN, TRANSLUCENT); params.gravity = Gravity.TOP | gravityEndLocaleHack(); return params; } @SuppressLint("RtlHardcoded") // Gravity.END is not honored by WindowManager for added views. private static int gravityEndLocaleHack() { int direction = getLayoutDirectionFromLocale(Locale.getDefault()); return direction == LAYOUT_DIRECTION_RTL ? Gravity.LEFT : Gravity.RIGHT; } interface Listener { /** Called when cancel is clicked. This view is unusable once this callback is invoked. */ void onCancel(); /** * Called when start is clicked and the view is animating itself out, * before {@link #onStart()}. */ void onPrepare(); /** * Called when start is clicked and it is appropriate to start recording. This view will hide * itself completely before invoking this callback. */ void onStart(); /** Called when stop is clicked. This view is unusable once this callback is invoked. */ void onStop(); /** Called when the size or layout params of this view have changed and require a relayout. */ void onResize(); } @BindView(R.id.record_overlay_buttons) View buttonsView; @BindView(R.id.record_overlay_cancel) View cancelView; @BindView(R.id.record_overlay_start) View startView; @BindView(R.id.record_overlay_stop) View stopView; @BindView(R.id.record_overlay_recording) TextView recordingView; @BindDimen(R.dimen.overlay_width) int animationWidth; private final Listener listener; private final boolean showCountDown; private OverlayView(Context context, Listener listener, boolean showCountDown) { super(context); this.listener = listener; this.showCountDown = showCountDown; inflate(context, R.layout.overlay_view, this); ButterKnife.bind(this); if (getLayoutDirectionFromLocale(Locale.getDefault()) == LAYOUT_DIRECTION_RTL) { animationWidth = -animationWidth; // Account for animating in from the other side of screen. } CheatSheet.setup(cancelView); CheatSheet.setup(startView); } @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = insets.getSystemWindowInsetTop(); listener.onResize(); return insets.consumeSystemWindowInsets(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); setTranslationX(animationWidth); animate().translationX(0) .setDuration(DURATION_ENTER_EXIT) .setInterpolator(new DecelerateInterpolator()); } @OnClick(R.id.record_overlay_cancel) void onCancelClicked() { animate().translationX(animationWidth) .setDuration(DURATION_ENTER_EXIT) .setInterpolator(new AccelerateInterpolator()) .withEndAction(new Runnable() { @Override public void run() { listener.onCancel(); } }); } @OnClick(R.id.record_overlay_start) void onStartClicked() { recordingView.setVisibility(VISIBLE); int centerX = (int) (startView.getX() + (startView.getWidth() / 2)); int centerY = (int) (startView.getY() + (startView.getHeight() / 2)); Animator reveal = createCircularReveal(recordingView, centerX, centerY, 0, getWidth() / 2f); reveal.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { buttonsView.setVisibility(GONE); } }); reveal.start(); postDelayed(new Runnable() { @Override public void run() { if (showCountDown) { showCountDown(); } else { countdownComplete(); } } }, showCountDown ? COUNTDOWN_DELAY : NON_COUNTDOWN_DELAY); } private void startRecording() { recordingView.setVisibility(INVISIBLE); stopView.setVisibility(VISIBLE); stopView.setOnClickListener(new OnClickListener() { @Override public void onClick(@NonNull View v) { listener.onStop(); } }); listener.onStart(); } private void showCountDown() { String[] countdown = getResources().getStringArray(R.array.countdown); countdown(countdown, 0); // array resource must not be empty } private void countdownComplete() { listener.onPrepare(); recordingView.animate() .alpha(0) .setDuration(COUNTDOWN_DELAY) .withEndAction(new Runnable() { @Override public void run() { startRecording(); } }); } private void countdown(final String[] countdownArr, final int index) { postDelayed(new Runnable() { @Override public void run() { recordingView.setText(countdownArr[index]); if (index < countdownArr.length - 1) { countdown(countdownArr, index + 1); } else { countdownComplete(); } } }, COUNTDOWN_DELAY); } }